/********************************************************************************
 * Copyright (c) 2011-2017 Red Hat Inc. and/or its affiliates and others
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License 1.0 which is available at
 * http://www.eclipse.org/legal/epl-v10.html.
 *
 * SPDX-License-Identifier: EPL-1.0
 ********************************************************************************/
package org.eclipse.ceylon.ide.eclipse.code.html;

import java.io.IOException;
import java.io.Reader;


/**
 * Reads the text contents from a reader and computes for each 
 * character a potential substitution. The substitution may eat 
 * more characters than only the one passed into the computation 
 * routine.
 */
public abstract class SubstitutionTextReader 
        extends SingleCharReader {

    protected static final String LINE_DELIM = 
            System.getProperty("line.separator", "\n");

    private Reader fReader;
    protected boolean fWasWhiteSpace;
    private int fCharAfterWhiteSpace;

    /**
     * Tells whether white space characters are skipped.
     */
    private boolean fSkipWhiteSpace = true;

    private boolean fReadFromBuffer;
    private StringBuffer fBuffer;
    private int fIndex;


    protected SubstitutionTextReader(Reader reader) {
        fReader = reader;
        fBuffer = new StringBuffer();
        fIndex = 0;
        fReadFromBuffer = false;
        fCharAfterWhiteSpace = -1;
        fWasWhiteSpace = true;
    }

    /**
     * Computes the substitution for the given character and 
     * if necessary subsequent characters. Implementation 
     * should use <code>nextChar</code> to read subsequent 
     * characters.
     *
     * @param c the character to be substituted
     * @return the substitution for <code>c</code>
     * @throws IOException in case computing the substitution 
     * fails
     */
    protected abstract String computeSubstitution(int c) 
            throws IOException;

    protected Reader getReader() {
        return fReader;
    }

    /**
     * Returns the next character.
     * @return the next character
     * @throws IOException in case reading the character fails
     */
    protected int nextChar() 
            throws IOException {
        fReadFromBuffer= (fBuffer.length() > 0);
        if (fReadFromBuffer) {
            char ch= fBuffer.charAt(fIndex++);
            if (fIndex >= fBuffer.length()) {
                fBuffer.setLength(0);
                fIndex= 0;
            }
            return ch;
        }

        int ch= fCharAfterWhiteSpace;
        if (ch == -1) {
            ch= fReader.read();
        }
        if (fSkipWhiteSpace && 
                Character.isWhitespace((char)ch)) {
            do {
                ch= fReader.read();
            }
            while (Character.isWhitespace((char)ch));
            if (ch != -1) {
                fCharAfterWhiteSpace= ch;
                return ' ';
            }
        }
        else {
            fCharAfterWhiteSpace= -1;
        }
        return ch;
    }

    public int read() 
            throws IOException {
        int c;
        do {

            c= nextChar();
            while (!fReadFromBuffer && c != -1) {
                String s= computeSubstitution(c);
                if (s == null)
                    break;
                if (s.length() > 0)
                    fBuffer.insert(0, s);
                c= nextChar();
            }

        }
        while (fSkipWhiteSpace && fWasWhiteSpace && (c == ' '));
        fWasWhiteSpace = (c == ' ' || c == '\r' || c == '\n');
        return c;
    }

    public boolean ready() 
            throws IOException {
        return fReader.ready();
    }

    public void close() 
            throws IOException {
        fReader.close();
    }

    public void reset() 
            throws IOException {
        fReader.reset();
        fWasWhiteSpace = true;
        fCharAfterWhiteSpace = -1;
        fBuffer.setLength(0);
        fIndex= 0;
    }

    protected final void setSkipWhitespace(boolean state) {
        fSkipWhiteSpace= state;
    }

    protected final boolean isSkippingWhitespace() {
        return fSkipWhiteSpace;
    }
}
